docs(adr): ADR-0092 Tier 1 is {name, image, locale} — amended in place per the #14787 ruling (#14951) - #15752
Merged
Merged
Conversation
The 2026-09-03 maintainer ruling on #14787 widened the ADR-0092 D2 self-service whitelist to three fields, and the code has said so since: SYS_USER_PROFILE_EDIT_FIELDS in packages/plugins/plugin-auth/src/sys-user-writable-fields.ts holds {name, image, locale}. ADR-0092 still recorded the two-field set as the decision, which is the condition under which a later PR "restores" the old behaviour in good faith. Amend ADR-0092 in place per the vehicle ruling on this card (A: dated revision line + D1 rewritten, no superseding ADR): - Date line gains a 2026-09-05 amendment entry, in the file's own revision convention. - D1's Tier 1 list gains `locale`, with a D1 Amendment blockquote recording the four binding points. - TL;DR D1 and D2 bullets, and the D2 Behaviour sentence, restate the three-field set. - D6 gains a note stating the mirror set and Tier 1 are no longer the same set, naming SESSION_SNAPSHOT_MIRRORED_FIELDS in packages/plugins/plugin-auth/src/identity-write-guard.ts. - The D5 Amendment's closing "Not amended by this" paragraph is trued up: the table it said was unreconciled is reconciled here. - The D3 design sketch is left verbatim and annotated, so a code-shaped two-field literal is not read as the live value. - ADR-0105's passing two-field spelling is corrected. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
The three constants the amendment cites were written as bare backticked paths. `docs/adr/**` sets the symbol-anchor corpus's `checkBarePaths` off (a measured corpus decision -- judging every bare path there yields 1,056 findings, almost none of them anchors), so a bare path is counted but never resolved: the citation could rot silently. Rewritten in the symbol-anchor form the gate does resolve, which also satisfies the ruling's "cite it by name" more literally than prose did. check-adr-symbol-anchors goes 186 -> 189 symbol anchors and 167 -> 170 declaration anchors, 0 hard findings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
os-zhuang
approved these changes
Sep 5, 2026
os-zhuang
marked this pull request as ready for review
September 5, 2026 06:11
os-zhuang
enabled auto-merge
September 5, 2026 06:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #14951
What this is
ADR-0092's D1 tier table still recorded Tier 1 as
{name, image}while the enforcedconstant has held
{name, image, locale}since the 2026-09-03 maintainer ruling on#14787. This amends ADR-0092 in place per the vehicle ruling on this card, and
corrects ADR-0105's passing two-field spelling.
Ruling followed (binding): director-seat record at
#14951 (comment) —
maintainer reply 「同意」 to option A: dated revision/status line + D1 tier table
rewritten, no superseding ADR; ADR-0105's same sentence corrected in the same PR.
Code truth verified before writing a word
The premise holds: the code says three, the ADR said two.
Before / after, every changed sentence
1. Date line (ADR-0092, the file's own revision convention — appended, nothing replaced)
… · **2026-09-03 (amended: D5 …)** · **2026-09-05 (amended: D1 — Tier 1 becomes {name, image, locale}, carrying the 2026-09-03 ruling on #14787; see the D1 Amendment)**2. TL;DR D1 bullet
sys_usercolumns into three tiers; onlynameandimageare profile-editable through the generic path."name,imageandlocaleare profile-editable through the generic path." plus a dated "Amended 2026-09-05" line quoting the superseded wording.3. TL;DR D2 bullet
sys_user → {name, image}is its first entry."sys_user → {name, image, locale}is its first entry ({name, image}as originally shipped — see the D1 Amendment)."4. D1 Tier 1 list — gains a third member:
locale— preferred BCP-47 language tag. No auth semantics: not a login key, not authorization state, and better-auth is oblivious to it. Admitted 2026-09-03 by maintainer ruling on [Decision] May a user set their ownsys_user.locale? — the ADR-0092 D2 self-service whitelist stays{name, image}after #13881 (column landsreadonly, system-context writes only) #14787; shape-checked at the write by the column's ownlocale_bcp47_shaperule."D1 is the one section rewritten rather than kept-verbatim-plus-note, and the section
says why: it is the set the D2 guard is read against, so a reader taking the old list as
current would delete an enforced member in good faith. The superseded wording is quoted
inside the Amendment, so nothing is lost.
5. D2 Behaviour
sys_user → SYS_USER_PROFILE_EDIT_FIELDS(D1 Tier 1)."{name, image, locale}since the 2026-09-05 amendment,{name, image}as originally shipped). Still the only entry: what the amendment widened is that entry's field set, not the number of registered objects."6. D5 Amendment's closing paragraph — it asserted the very state this PR ends:
{name, image}— the 2026-09-03 ruling made the shipped set{name, image, locale}#14951)."{name, image}— the 2026-09-03 ruling made the shipped set{name, image, locale}#14951 closed that gap on 2026-09-05 — the table now holds{name, image, locale}too."7. D6 gains an amendment note stating the two sets are no longer the same set.
8. D3's design sketch is left verbatim (it is dated 2026-07-10 design text) and
annotated beneath, so its
new Set(['name', 'image'])literal is not read as the livevalue. Flagged for reviewers: the alternative treatment is to edit the snippet itself —
say the word and I will.
9. ADR-0105 F4
sys_user → {name, image}is its first entry)"sys_user → {name, image, locale}is its first entry —localeadmitted 2026-09-03 by maintainer ruling on [Decision] May a user set their ownsys_user.locale? — the ADR-0092 D2 self-service whitelist stays{name, image}after #13881 (column landsreadonly, system-context writes only) #14787; see the ADR-0092 D1 Amendment)"Deliberately NOT changed (each is correct as written, and the ADR house form keeps
original text verbatim): the Context table row "Self profile (
name,image) |better-auth
/update-user" — that describes what better-auth's own endpoint carries, andit carries two; the Context note "for the D1 whitelist (
name,image) staleness iscosmetic … cosmetic until someone widens the whitelist" — answered in the new D6 note
rather than rewritten, since it turned out prophetic; and the two past-tense
{name, image}references inside the D5 Amendment's own narration.
The ruling's binding point 4 says to restate D5 including "
member_defaultstilldenies
allowEditonsys_user", and to record member self-service onlocaleasOPEN. Against
origin/mainthat is no longer true, and ADR-0092 itself alreadysays so:
packages/plugins/plugin-security/src/objects/default-permission-sets.ts:529, inside themember_defaultset (declared at line 391):sys_user: { allowRead: true, allowCreate: false, allowEdit: true, allowDelete: false }sys_user.locale? Ruling B opened the COLUMN; ADR-0092 D5 still answers no to the ROUTE, so today only admins can set it #14959, verbatim 「同意」) recordingexactly that decision.
So writing point 4 literally would have created a fresh contradiction between the governing
document and the governed code — the defect this card exists to remove — and would have
regressed the ADR's own D5 Amendment. I therefore recorded point 4's mechanism verbatim
(whitelist decides which columns, permission sets decide who; widening one does not widen
the other) and recorded as OPEN the question that genuinely still is open and that both
the original D5 text and its Amendment leave open: an org admin editing another member's
row (
sys_user_org_membersstaysselect-only precisely so it cannot compose into thatwrite). Nothing is resolved that was not already ruled.
If reviewers prefer point 4's literal wording, that is a maintainer call and I will change it.
Verification — every exit code captured before any pipe
Gate set derived, not guessed:
node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack(EXIT=0) → 17 commands. All 17 run at head
f875a58a3, all EXIT=0:node scripts/check-adr-links.mjs(+--self-test)node scripts/check-adr-symbol-anchors.mjs(+--self-test)node scripts/check-ci-filter-parity.mjsnode scripts/check-closing-keyword-parity.mjs(+--self-test)node scripts/check-comment-mask-corpus.mjspnpm --filter @objectstack/lint run check:doc-formula-expressionspnpm check:adr-anchorspnpm check:cross-package-test-inputspnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:nul-bytespnpm check:pm-governed-mergespnpm check:refd-timer-probepnpm check:watch-hint-literalVerdict lines quoted from the gates themselves:
✅ check-adr-symbol-anchors: 1925 anchors across 135 records resolve — 189 symbol (170 declaration, 19 literal), 1721 file-level, 15 cross-repo, 6 exempt, 2 continuation. 0 line anchors survive.check-nul-bytes: OK (scanned 7610 text file(s) -- 7610 tracked, 0 untracked-not-ignored; skipped 7 binary, 1 non-regular; no raw ASCII control bytes).✓ check-governed-merges --self-test: 274 assertions …✓ doc authoring guard: 394 files clean — no bare metadata literals.Governed-surface predicate, quoted:
Reverse verification (the citations are gate-enforced, not just prose)
The three code citations were first written as bare backticked paths.
docs/adr/**deliberately sets the symbol-anchor corpus's
checkBarePathsoff (a measured corpusdecision — judging every bare path there yields 1,056 findings, almost none of them
anchors), so a bare path is counted but never resolved. The second commit rewrites them
in symbol-anchor form, which the gate does resolve. Proof, both legs, on a committed tree:
SESSION_SNAPSHOT_MIRRORED_FIELDS→…FIELDZ; mutation confirmed ondisk (2 injected occurrences, blob
82011c7→a8d39d1); gate EXIT=1 with[unresolved-symbol] … has no declaration site or string-literal token in packages/plugins/plugin-auth/src/identity-write-guard.ts.git checkout HEAD -- ABSPATH; blob back to82011c7, 0 markeroccurrences, gate EXIT=0.
Counts moved the expected direction: symbol anchors 186 → 189, declaration 167 → 170.
NOT MEASURED, by name
scripts/check-cross-package-test-inputs.mjs --union-into "$RUNNER_TEMP/…" --changed "$RUNNER_TEMP/…"scripts/check-shard-attestation.mjs --emit --job test --shard MATRIX.SHARD --total 6 --out "$RUNNER_TEMP/…"scripts/check-test-completeness.mjs "$RUNNER_TEMP/test-core.log" --scheduled … --package-list …All three take a value that only exists inside a CI run; the derivation names them as
NOT MEASURED rather than green. CI measures them.
No repo-wide
pnpm lintrun: this diff is two Markdown files underdocs/adr/**, outsidethe ESLint corpus entirely.
No changeset — the exemption, quoted
AGENTS.md: "⛔ never
skip-changeset: that label is for a diff that publishes nothing fromany released package." This diff is two
docs/adr/**Markdown files and publishes nothingfrom any package, so the label applies and no changeset is owed. No anchor JSON under
scripts/adr-anchors/was needed —pnpm check:adr-anchorspasses as-is, and no file wasadded beyond the two ADRs.
Governed surface — draft, human merge, reviewers os-zhuang + hotlong
docs/adr/**is governed (Prime Directive #14). This PR stays a draft: never flippedready, never enqueued, never auto-merged. Review requested from
os-zhuangandhotlong;a human performs the merge.
Files changed:
docs/adr/0092-sys-user-profile-field-delegation.md,docs/adr/0105-group-tenancy-posture-and-first-class-org-scope.md— nothing else.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code